package j2metris.game;

import j2metris.ControllablePiece;
import j2metris.Coordinate;
import j2metris.Direction;
import j2metris.J2metrisApp;
import j2metris.PieceGenerator;
import j2metris.Settings;
import j2metris.Well;

import j2metris.pieces.PieceManager;

import j2metris.ui.GameScreen;

import j2metris.util.StoppableRunnable;

import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Image;

public class Game implements Settings
{

    private final class GravityManager extends StoppableRunnable
    {
        private final int delay;
        public GravityManager(int delayInMilliseconds)
        {
            delay = delayInMilliseconds;
        }

        public void start() {}

        public boolean shouldContinue()
        {
            return true;
        }

        public void handle(Exception e) {}

        public void loop() throws InterruptedException
        {
            Thread.sleep(delay);
            moveActivePieceIfPossible(Direction.DOWN);
        }

        public void finish() {};

    }
    
    private static Game instance;
    
    private final PieceManager pieceList;
    private final int unitWidth;
    private final int unitHeight;
    private final int delayInMilliseconds;
    private final PieceGenerator pieceGenerator;
    private final Display display =
            Display.getDisplay(J2metrisApp.getInstance());
    private final Well well;
    private final int initialMovementDelay;
    private final int mainMovementDelay;
    private final int initialRotationDelay;
    private final int mainRotationDelay;
    private final GameScreen screen;
    private ControllablePiece holdPiece;
    private boolean gravityShouldStop = false;
    private boolean mainLoopShouldStop = false;
    private GravityManager gravityManager;
    private Thread gravityThread;

    /**
     * The <code>ControllablePiece</code> that the player is currently controlling.
     */
    private PieceWithPosition activePiece;

    private Game(Settings settings)
    {
        pieceList = settings.getPieceList();
        unitHeight = settings.getUnitHeight();
        unitWidth = settings.getUnitWidth();
        delayInMilliseconds = settings.getDelayInMilliseconds();
        pieceGenerator = settings.getPieceGenerator();
        well = new Well(settings.getWellWidth(), settings.getWellHeight(),
                settings.getBackgroundImage());
        initialMovementDelay = settings.getInitialMovementDelay();
        mainMovementDelay = settings.getMainMovementDelay();
        initialRotationDelay = settings.getInitialRotationDelay();
        mainRotationDelay = settings.getMainRotationDelay();
        screen = new GameScreen(true, initialRotationDelay, mainRotationDelay,
                initialMovementDelay, mainMovementDelay);
        gravityManager = new GravityManager(delayInMilliseconds);
        gravityThread = new Thread(gravityManager);
    }

    public static void createGame(Settings settings)
    {
        instance = new Game(settings);
        if(J2metrisApp.DEBUG) System.err.println("GAME INITIALIZED");
    }

    public static Game getInstance()
    {
        return instance;
    }

    public PieceManager getPieceList()
    {
        return pieceList;
    }

    public int getUnitWidth()
    {
        return unitWidth;
    }

    public int getUnitHeight()
    {
        return unitHeight;
    }

    public int getDelayInMilliseconds()
    {
        return delayInMilliseconds;
    }

    public PieceGenerator getPieceGenerator()
    {
        return pieceGenerator;
    }

    public int getWellWidth()
    {
        return well.getWidth();
    }

    public int getWellHeight()
    {
        return well.getHeight();
    }

    public Image getBackgroundImage()
    {
        return well.getBackgroundImage();
    }

    public int getInitialMovementDelay()
    {
        return initialMovementDelay;
    }

    public int getMainMovementDelay()
    {
        return mainMovementDelay;
    }

    public int getInitialRotationDelay()
    {
        return initialRotationDelay;
    }

    public int getMainRotationDelay()
    {
        return mainRotationDelay;
    }

    private boolean gameIsLost()
    {
        return false;
//        throw new UnsupportedOperationException();
    }

    public void stopGravity()
    {
        gravityShouldStop = true;
    }

    public boolean gravityShouldStop()
    {
        return gravityShouldStop;
    }

    public void stopMainLoop()
    {
        mainLoopShouldStop = true;
    }

    private void setActivePiece(ControllablePiece p)
    {
        activePiece = new PieceWithPosition(p, new Coordinate(5, 0));
        System.err.println("ACTIVE PIECE SET TO " + p);
    }

    public Coordinate getActivePiecePosition()
    {
        return activePiece.getPosition();
    }

    public GameScreen getScreen()
    {
        return screen;
    }

    public ControllablePiece getActivePiece()
    {
        return activePiece;
    }

    public Well getWell()
    {
        return well;
    }

    /**
     * Puts the active <code>ControllablePiece</code> into the hold box, and switches
     * in any <code>ControllablePiece</code> that is currently in the hold box.
     */
    public synchronized void putActivePieceIntoHoldBox()
    {
        if(holdPiece == null)
        {
            holdPiece = activePiece;
            setActivePiece(pieceGenerator.popNextPiece());
        }
        else
        {
            ControllablePiece tmp = holdPiece;
            holdPiece = activePiece;
            setActivePiece(tmp);
        }
    }

    /**
     * Drops the active <code>ControllablePiece</code> to the bottom and immediately locks
     * it in place.
     */
    public synchronized void dropActivePiece()
    {
        gravityManager.stop();
        PieceWithPosition clone = (PieceWithPosition) activePiece.clone();
        while(!well.areObstructed(clone.getAbsolutePositions(), Direction.DOWN))
        {
            clone.setPosition(clone.getPosition().getX(),
                    clone.getPosition().getY() + 1);
        }
        activePiece.setPosition(clone.getPosition());
        screen.drawGame();
    }

    /**
     * Checks to see if the active piece can be moved in the specified Direction,
     * and if it can, moves it in that direction.
     *
     * The check and the motion must be implemented in one synchronized method.
     * If they were not, it would be possible for one thread to do a check while
     * another thread was doing the motion; when the original thread tried
     * to move the piece, it would be in a different position than expected,
     * and illegal behavior might occur.
     * 
     * @param direction the direction in which to move the current active Piece
     */
    public synchronized void moveActivePieceIfPossible(Direction direction)
    {
        if(direction.isARotationDirection())
        {
            if(!well.areObstructed(activePiece.getAbsolutePositions(
            (Direction.RotationDirection) direction, 1)))
            {
                Coordinate[] currentPosition = activePiece.getAbsolutePositions();
                activePiece.rotate((Direction.RotationDirection) direction, 1);
                screen.drawPiece(currentPosition, activePiece.getAbsolutePositions());
            }
        }
        else
        {
            if(!well.areObstructed(
                    activePiece.getAbsolutePositions(),
                    (Direction.TranslationDirection) direction))
            {
                Coordinate[] currentPosition = activePiece.getAbsolutePositions();
                activePiece.setPosition(
                        (Direction.TranslationDirection) direction, 1);
                screen.drawPiece(currentPosition, activePiece.getAbsolutePositions());
            }
        }
//        screen.drawGame();
    }

    //the game loop
    public void play()
    {
        display.setCurrent(screen);
        if(J2metrisApp.DEBUG) System.err.println("SCREEN SET");
        while(!gameIsLost())
        {
            setActivePiece(pieceGenerator.popNextPiece());
            gravityThread.start();
            screen.drawGame();
            while(!well.areObstructed(activePiece.getAbsolutePositions(), Direction.DOWN))
            {
                screen.processAndReactToInput();
            }
            gravityManager.stop();
            gravityThread.interrupt();
            well.lockPiece(activePiece, activePiece.getPosition());

            //clear rows if necessary.
            //to avoid unnecessary overhead, keep a list of already-checked rows
            int[] alreadyCheckedRows = new int[4];
            Coordinate[] positions = activePiece.getAbsolutePositions();
            int rowIntValue;
ROW_CHECK:
            for(int i = 0; i < positions.length; ++i)
            {
                rowIntValue = positions[i].getY();
                //then check that list to make sure we haven't already checked the current row
                for(int ii = 0; ii < i; ++ii)
                    if(alreadyCheckedRows[ii] == rowIntValue)
                        continue ROW_CHECK;
                //current row has not been checked; add it to the list
                alreadyCheckedRows[i] = rowIntValue;
                //check the current row and clear it if it is full
                if(well.rowIsFull(rowIntValue))
                {
                    well.clearLine(rowIntValue);
                }
                //redraw
                screen.drawGame();
            }
            try
            {
                gravityThread.join();
            }
            catch(InterruptedException e)
            {
                return;
            }
            gravityManager = new GravityManager(delayInMilliseconds);
            gravityThread = new Thread(gravityManager);
        }
    }
}
